package com.atguigu.tingshu.search.service.impl;
import co.elastic.clients.elasticsearch.core.search.*;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.*;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.NestedQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.ServiceSearchApplication;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;

    @Autowired
    private ElasticsearchClient elasticsearchClient;
    @Autowired
    private ServiceSearchApplication serviceSearchApplication;

    @SneakyThrows
    @Override
    public List<Map<String, Object>> channel(Long category1Id) {
        //1.根据一级分类id 查询一级分类下7个指定的3级分类集合
        Result<List<BaseCategory3>> category3ListByC1Id =  albumInfoFeignClient.getBaseCategory3ListByC1Id(category1Id);
        List<BaseCategory3> category3ListByC1IdData = category3ListByC1Id.getData();

        if (CollectionUtils.isEmpty(category3ListByC1IdData)){
            throw new GuiguException(201,"远成调用分类信息失败");
        }
        //2.过滤获取到三级分类的id集合 而且转成FieldValue类型 （Es在查询的时候要用到这个类型）
        List<FieldValue> c3IdsFieldValue = category3ListByC1IdData.stream().map(baseCategory3 -> {
            return FieldValue.of(baseCategory3.getId());
        }).collect(Collectors.toList());

        //3.在将三级分类集合转成一个map
        Map<Long, BaseCategory3> c3IdAndBaseCategory3 = category3ListByC1IdData.stream().collect(Collectors.toMap((BaseCategory3::getId), v -> v));

        //4.构建dsl语句
        SearchRequest searchRequest = buildChannelDsl(c3IdsFieldValue);
        //5.查询es
        SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);

        //6.解析es的返回数据
        List<Map<String,Object>> result = parseChannelData(response,c3IdAndBaseCategory3);
        return result;
    }

    @SneakyThrows
    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        //1. 构建带条件的查询dsl语句
        SearchRequest searchRequest = buildSearchDsl(albumIndexQuery);

        //2. 开始查询es，得到响应对象
        SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(searchRequest , AlbumInfoIndex.class);

        //3. 解析从es中返回的数据，封装给前端
        AlbumSearchResponseVo albumSearchResponseVo = parseSearchData(response);
        albumSearchResponseVo.setPageSize(albumIndexQuery.getPageSize());
        albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());

        Long total = albumSearchResponseVo.getTotal();
        Long totalPages = total % albumIndexQuery.getPageSize() == 0 ? total / albumIndexQuery.getPageSize() : total / albumIndexQuery.getPageSize() + 1;
        albumSearchResponseVo.setTotalPages(totalPages);  //总页数


        //4. 返回数据给前端
        return albumSearchResponseVo;


    }


    @SneakyThrows
    @Override
    public Set<String> completeSuggest(String input) {
        // 1.构建带条件的查询dsl语句
        SearchRequest searchRequest = buildCompleteDsl(input);
        // 2.开始查询es，得到响应对象
        SearchResponse<SuggestIndex> response = elasticsearchClient.search(searchRequest, SuggestIndex.class);
        // 3.解析从es中返回的数据，封装给前端

        Set<String> suggestIndexSet =  parseCompleteData(response);

        if (suggestIndexSet.size() < 10){
            SearchResponse<SuggestIndex> searchResponse = elasticsearchClient.search(sb -> sb
                    .index("suggestinfo")
                    .query(qb -> qb
                            .match(mqb -> mqb
                                    .field("title")
                                    .query(input))), SuggestIndex.class);
            for (Hit<SuggestIndex> hit : searchResponse.hits().hits()) {
                SuggestIndex suggestIndex = hit.source();
                String title = suggestIndex.getTitle();
                suggestIndexSet.add(title);
                if (suggestIndexSet.size() >= 10){  //够10就返回
                    break;
                }
            }

        }

        // 4.返回数据给前端
        return suggestIndexSet;
    }


    private SearchRequest buildCompleteDsl(String input) {
        // 1.创建SearchRequestBuilder对象
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
        // 2.创建SuggesterBuilder对象
        Suggester.Builder suggesterBuilder = new Suggester.Builder();

        // 3.拼接提示词的dsl
        suggesterBuilder
                .suggesters("suggestionKeyword",fsb -> fsb
                    .prefix(input)
                    .completion(csb -> csb
                            .field("keyword")))

                .suggesters("suggestionKeywordPinyin",fsb -> fsb
                        .prefix(input)
                        .completion(csb -> csb
                                .field("keywordPinyin")))

                .suggesters("suggestionKeywordSequence",fsb -> fsb
                        .prefix(input)
                        .completion(csb -> csb
                                .field("keywordSequence")));

        // 4.得到suggester
        Suggester suggester = suggesterBuilder.build();

        SearchRequest searchRequest = searchRequestBuilder.index("suggestinfo").suggest(suggester).build();

        System.out.println("提示词的dsl = " + searchRequest.toString());
        return searchRequest;
    }

    private Set<String> parseCompleteData(SearchResponse<SuggestIndex> response) {
        HashSet<String> result = new HashSet<>();
        //Map中有三队key，value
        //第一对：key ：suggestionKeyword  value:List<满足条件的内容>
        //第二对：key ：suggestionKeywordPinyin  value:List<满足条件的内容>
        //第二对：key ：suggestionKeywordSequence  value:List<满足条件的内容>

        Map<String, List<Suggestion<SuggestIndex>>> map = response.suggest();

        for (Map.Entry<String, List<Suggestion<SuggestIndex>>> stringListEntry : map.entrySet()) { //遍历三次

            List<Suggestion<SuggestIndex>> value = stringListEntry.getValue();

            for (Suggestion<SuggestIndex> suggestIndexSuggestion : value) { //遍历一次

                List<CompletionSuggestOption<SuggestIndex>> options = suggestIndexSuggestion.completion().options();
                for (CompletionSuggestOption<SuggestIndex> option : options) {
                    SuggestIndex suggestIndex = option.source();
                    String title = suggestIndex.getTitle(); //去显示的
                    result.add(title);
                }
            }
        }
        return result;
    }





    private SearchRequest buildSearchDsl(AlbumIndexQuery albumIndexQuery) {
        // 1.构建最外层{}
        SearchRequest.Builder searchBuilder = new SearchRequest.Builder();

        // 2.构建bool的查询{}
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();

        // 3.用boolQueryBuilder运行查询（should以及must）
        // 3.1 判断关键字是否携带
        String keyword = albumIndexQuery.getKeyword();

        if (!StringUtils.isEmpty(keyword)){
            boolQueryBuilder.should(qb -> qb.match(mqb -> mqb.field("albumTitle").query(keyword)));
            boolQueryBuilder.should(qb -> qb.match(mqb -> mqb.field("albumIntro").query(keyword)));
            boolQueryBuilder.should(qb -> qb.match(mqb -> mqb.field("announcerName").query(keyword)));
        }

        // 3.2 判断分类Id
        // a) 一级分类Id是否为空
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id != null){
            boolQueryBuilder.must(qb -> qb.term(tqb -> tqb.field("category1Id").value(category1Id)));
        }
        // b) 二级分类Id是否为空
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id != null){
            boolQueryBuilder.must(qb -> qb.term(tqb -> tqb.field("category2Id").value(category2Id)));
        }
        // c) 三级分类是否为空
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id != null){
            boolQueryBuilder.must(qb -> qb.term(tqb -> tqb.field("category3Id").value(category3Id)));
        }

        // 3.3 判断标签是否为空
        //String: "1:2" "2:4"
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (!CollectionUtils.isEmpty(attributeList)){
            for (String attrAndValueId : attributeList) {
                String[] split = attrAndValueId.split(":");
                String attrId = split[0]; //属性Id
                String valueId = split[1]; //属性值Id获取到了

                NestedQuery.Builder builder = new NestedQuery.Builder();
                builder
                        .path("attributeValueIndexList")
                        .query(qb -> qb
                                .bool(bqb -> bqb
                                        .must(mqb -> mqb
                                                .term(tqb -> tqb
                                                        .field("attributeValueIndexList.attributeId")
                                                        .value(attrId)
                                                )
                                        )
                                        .must(mqb -> mqb
                                                .term(tqb -> tqb
                                                        .field("attributeValueIndexList.valueId")
                                                        .value(valueId)
                                                )
                                        )
                                )
                        );
                // 得到NestQuery对象
                NestedQuery nestedQuery = builder.build();
                boolQueryBuilder.must(qb -> qb.nested(nestedQuery));

            }
        }

        BoolQuery boolQuery = boolQueryBuilder.build();
        Query query = boolQuery._toQuery();
        searchBuilder.index("albuminfo").query(query);

        //===================基本条件的dsl语句编写完毕==========================

        // 3.4判断分页
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();

        Integer from = (pageNo -1) * pageSize;

        searchBuilder.from(from).size(pageSize);

        // 3.5判断排序 (1:desc[asc])  (2:desc[asc])  (3:desc[asc])
        String order = albumIndexQuery.getOrder();
        if (!StringUtils.isEmpty(order)){
            String[] split = order.split(":");
            String orderDimension = split[0];  //纬度
            String rule = split[1];   //规则

            SortOrder sortOrder = rule.equals("desc") ? SortOrder.Desc : SortOrder.Asc;

            switch (orderDimension){
                case "1":
                    //处理热度值
                    searchBuilder.sort(sob ->sob.field(fsb -> fsb.field("hotScore").order(sortOrder)));
                    break;
                case "2":
                    //处理购买量
                    searchBuilder.sort(sob ->sob.field(fsb -> fsb.field("buyStatNum").order(sortOrder)));
                    break;
                case "3":
                    //处理发布时间
                    searchBuilder.sort(sob ->sob.field(fsb -> fsb.field("createTime").order(sortOrder)));
                    break;
            }


        } else { //默认按照热度值降序排序
            searchBuilder.sort(sob ->sob.field(fsb -> fsb.field("hotScore").order(SortOrder.Desc)));
        }

        // 3.6组合高亮
        searchBuilder.highlight(hb -> hb
                .fields("albumTitle",hfb -> hfb
                        .preTags("<font style='color:red'>")
                        .postTags("</font>")
                )
        );

        // 4返回SearchRequest对象
        SearchRequest searchRequest = searchBuilder.build();
        System.out.println("搜索的dsl语句 ： " + searchRequest);
        return searchRequest;

    }

    private AlbumSearchResponseVo parseSearchData(SearchResponse<AlbumInfoIndex> response) {
        AlbumSearchResponseVo albumSearchResponseVo = new AlbumSearchResponseVo();

        TotalHits totalHits = response.hits().total();
        long value = totalHits.value();
        albumSearchResponseVo.setTotal(value);  //总专辑数

        List<AlbumInfoIndexVo> albumInfoIndexVoList = response.hits().hits().stream().map(albumInfoIndexHit -> {
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            AlbumInfoIndex albumInfoIndex = albumInfoIndexHit.source();
            BeanUtils.copyProperties(albumInfoIndex, albumInfoIndexVo);

            //处理高亮
            Map<String, List<String>> highlight = albumInfoIndexHit.highlight();
            List<String> albumTitle = highlight.get("albumTitle");
            if (!CollectionUtils.isEmpty(albumTitle)) {
                String highlightAlbumTitle = albumTitle.get(0);
                albumInfoIndexVo.setAlbumTitle(highlightAlbumTitle);
            }
            return albumInfoIndexVo;

        }).collect(Collectors.toList());

        albumSearchResponseVo.setList(albumInfoIndexVoList);  //专辑集合

        return albumSearchResponseVo;
    }




    /**
     * 4.构建dsl语句
     * 查询
     * @param c3IdsFieldValue
     * @return
     */
    private SearchRequest buildChannelDsl(List<FieldValue> c3IdsFieldValue) {
        // 1.构建SearchRequestBuilder对象
        SearchRequest.Builder builder = new SearchRequest.Builder();

        // 2.构建查询
        builder.index("albuminfo")
               .query(b -> b
                    .terms(tqb -> tqb
                        .field("category3Id")
                        .terms(tqfb -> tqfb
                                .value(c3IdsFieldValue))))
               .aggregations("category3IdAgg",ab -> ab
                       .terms(tab -> tab
                               .field("category3Id")
                               .size(c3IdsFieldValue.size()))
                       .aggregations("topHitHotScoreAgg",sab -> sab
                            .topHits(thab -> thab
                                    .sort(sob -> sob
                                            .field(fsb -> fsb
                                                    .field("hotScore")
                                                    .order(SortOrder.Desc)))
                                    .size(6))));

        SearchRequest searchRequest = builder.build();
        System.out.println("频道页的dsl： " + searchRequest.toString());
        // 3.返回SearchRequest对象

        return searchRequest;
    }


    /**
     * 6.解析es的返回数据
     * @param response
     * @param c3IdAndBaseCategory3
     * @return
     */
    private List<Map<String, Object>> parseChannelData(SearchResponse<AlbumInfoIndex> response, Map<Long, BaseCategory3> c3IdAndBaseCategory3) {

        ArrayList<Map<String, Object>> result = new ArrayList<>();

        //1.得到category3IdAgg聚合对象
        Aggregate category3IdAgg = response.aggregations().get("category3IdAgg");

        //2.得到Aggregate具体的类型

        LongTermsAggregate longTermsAggregate = category3IdAgg.lterms();


        //3.得到父聚合的桶
        List<LongTermsBucket> buckets = longTermsAggregate.buckets().array();

        for (LongTermsBucket bucket : buckets) {

            //得到子聚合
            Aggregate topHitHotScoreAgg = bucket.aggregations().get("topHitHotScoreAgg");
            // 把子聚合转为精准的类型
            TopHitsAggregate topHitsAggregate = topHitHotScoreAgg.topHits();
            ArrayList<AlbumInfoIndex> albumInfoIndices = new ArrayList<>();
            for (Hit<JsonData> hit : topHitsAggregate.hits().hits()) {
                JsonData source = hit.source();
                AlbumInfoIndex albumInfoIndex = JSONObject.parseObject(source.toString(), AlbumInfoIndex.class);
                albumInfoIndices.add(albumInfoIndex);
            }


            Map<String, Object> map = new HashMap<>();

            long category3Id = bucket.key();

            map.put("baseCategory3",c3IdAndBaseCategory3.get(category3Id));
            map.put("list",albumInfoIndices);//当前二级分类下热度偏高的6个专辑集合
            result.add(map);
        }

        return result;

    }


}
